/**
 * Retrieved from http://d3s.mff.cuni.cz/~holub/sw/javamurmurhash/MurmurHash.java
 * 8. feb 2011
 */

package com.skjegstad.mist2.hash;

/** Murmur hash 2.0.
 *
 * The murmur hash is a relative fast hash function from
 * http://murmurhash.googlepages.com/ for platforms with efficient
 * multiplication.
 *
 * This is a re-implementation of the original C code plus some
 * additional features.
 *
 * Public domain.
 *
 * @author Viliam Holub
 * @version 1.0.2
 *
 */
public final class MurmurHash {

	/** Generates 32 bit hash from byte array of the given length and
	 * seed.
	 *
	 * @param data byte array to hash
         * @param first byte in array
	 * @param length length of the array to hash
	 * @param seed initial seed value
	 * @return 32 bit hash of the given array
	 */
	public static int hash32( final byte[] data, int start, int length, int seed) {
		// 'm' and 'r' are mixing constants generated offline.
		// They're not really 'magic', they just happen to work well.
		final int m = 0x5bd1e995;
		final int r = 24;
		// Initialize the hash to a random value
		int h = seed^length;
		int length4 = length/4;
                int start4 = start/4;

		for (int i=start4; i<start4+length4; i++) {
			final int i4 = i*4;
			int k = (data[i4+0]&0xff) +((data[i4+1]&0xff)<<8)
					+((data[i4+2]&0xff)<<16) +((data[i4+3]&0xff)<<24);
			k *= m;
			k ^= k >>> r;
			k *= m;
			h *= m;
			h ^= k;
		}

		// Handle the last few bytes of the input array
		switch (length%4) {
		case 3: h ^= (data[start+(length&~3) +2]&0xff) << 16;
		case 2: h ^= (data[start+(length&~3) +1]&0xff) << 8;
		case 1: h ^= (data[start+(length&~3)]&0xff);
				h *= m;
		}

		h ^= h >>> 13;
		h *= m;
		h ^= h >>> 15;

		return h;
	}


	/** Generates 32 bit hash from byte array with default seed value.
	 *
	 * @param data byte array to hash
	 * @param length length of the array to hash
	 * @return 32 bit hash of the given array
	 */
	public static int hash32( final byte[] data, int length) {
		return hash32( data, 0, length, 0x9747b28c);
	}


	/** Generates 32 bit hash from a string.
	 *
	 * @param text string to hash
	 * @return 32 bit hash of the given string
	 */
	public static int hash32( final String text) {
		final byte[] bytes = text.getBytes();
		return hash32( bytes, bytes.length);
	}


	/** Generates 32 bit hash from a substring.
	 *
	 * @param text string to hash
	 * @param from starting index
	 * @param length length of the substring to hash
	 * @return 32 bit hash of the given string
	 */
	public static int hash32( final String text, int from, int length) {
		return hash32( text.substring( from, from+length));
	}


	/** Generates 64 bit hash from byte array of the given length and seed.
	 *
	 * @param data byte array to hash
	 * @param length length of the array to hash
	 * @param seed initial seed value
	 * @return 64 bit hash of the given array
	 */
	public static long hash64( final byte[] data, int start, int length, long seed) {
		final long m = 0xc6a4a7935bd1e995L;
		final int r = 47;

		long h = seed^(length*m);

		int length8 = length/8;
                int start8 = start/8;

		for (int i=start8; i<start8+length8; i++) {
			final int i8 = i*8;
			long k =  ((long)data[i8+0]&0xff)      +(((long)data[i8+1]&0xff)<<8)
					+(((long)data[i8+2]&0xff)<<16) +(((long)data[i8+3]&0xff)<<24)
					+(((long)data[i8+4]&0xff)<<32) +(((long)data[i8+5]&0xff)<<40)
					+(((long)data[i8+6]&0xff)<<48) +(((long)data[i8+7]&0xff)<<56);

			k *= m;
			k ^= k >>> r;
			k *= m;

			h ^= k;
			h *= m;
		}

		switch (length%8) {
		case 7: h ^= (long)(data[start+(length&~7)+6]&0xff) << 48;
		case 6: h ^= (long)(data[start+(length&~7)+5]&0xff) << 40;
		case 5: h ^= (long)(data[start+(length&~7)+4]&0xff) << 32;
		case 4: h ^= (long)(data[start+(length&~7)+3]&0xff) << 24;
		case 3: h ^= (long)(data[start+(length&~7)+2]&0xff) << 16;
		case 2: h ^= (long)(data[start+(length&~7)+1]&0xff) << 8;
		case 1: h ^= (long)(data[start+(length&~7)]&0xff);
		        h *= m;
		};

		h ^= h >>> r;
		h *= m;
		h ^= h >>> r;

		return h;
	}


	/** Generates 64 bit hash from byte array with default seed value.
	 *
	 * @param data byte array to hash
	 * @param length length of the array to hash
	 * @return 64 bit hash of the given string
	 */
	public static long hash64( final byte[] data, int length) {
		return hash64( data, 0, length, 0xe17a1465);
	}


	/** Generates 64 bit hash from a string.
	 *
	 * @param text string to hash
	 * @return 64 bit hash of the given string
	 */
	public static long hash64( final String text) {
		final byte[] bytes = text.getBytes();
		return hash64( bytes, bytes.length);
	}


	/** Generates 64 bit hash from a substring.
	 *
	 * @param text string to hash
	 * @param from starting index
	 * @param length length of the substring to hash
	 * @return 64 bit hash of the given array
	 */
	public static long hash64( final String text, int from, int length) {
		return hash64( text.substring( from, from+length));
	}
}